import datetime

import dateutil
import functools
import itertools
import io
import logging
import operator
import pytz
import re
import uuid
from collections import defaultdict, MutableMapping, OrderedDict
from contextlib import closing
from inspect import getmembers, currentframe
from operator import attrgetter, itemgetter

import babel.dates
import dateutil.relativedelta
import psycopg2, psycopg2.extensions
from lxml import etree
from lxml.builder import E
from psycopg2.extensions import AsIs

import odoo
from odoo.tools import pycompat, collections
from . import SUPERUSER_ID
from . import api
from . import tools
from .exceptions import AccessError, MissingError, ValidationError, UserError
from .osv.query import Query
from .tools import frozendict, lazy_classproperty, lazy_property, ormcache
from .tools.config import config
from .tools.func import frame_codeinfo
from .tools.misc import CountingStream, clean_context, DEFAULT_SERVER_DATETIME_FORMAT, DEFAULT_SERVER_DATE_FORMAT
from .tools.safe_eval import safe_eval
from .tools.translate import _
from .tools import date_utils
from .tools import pycompat

_logger = logging.getLogger(__name__)
_schema = logging.getLogger(__name__ + '.schema')
_unlink = logging.getLogger(__name__ + '.unlink')

regex_order = re.compile('^(\s*([a-z0-9:_]+|"[a-z0-9:_]+")(\s+(desc|asc))?\s*(,|$))+(?<!,)$', re.I)
regex_object_name = re.compile(r'^[a-z0-9_.]+$')
regex_pg_name = re.compile(r'^[a-z_][a-z0-9_$]*$', re.I)
regex_field_agg = re.compile(r'(\w+)(?::(\w+)(?:\((\w+)\))?)?')

AUTOINIT_RECALCULATE_STORED_FIELDS = 1000

def check_object_name(name):
    if regex_object_name.match(name) is None:
        return False
    return True

def raise_on_invalid_object_name(name):
    if not check_object_name(name):
        msg = "The _name attribute %s is not valid." % name
        raise ValueError(msg)

def check_pg_name(name):
    if not regex_pg_name.match(name):
        raise ValidationError("Invalid characters in table name %r" % name)
    if len(name) > 63:
        raise ValidationError("Table name %r is too long" % name)

regex_private = re.compile(r'^(_.*|init)$')

def check_method_name(name):
    if regex_private.match(name):
        raise AccessError(_('Private methods (such as %s) cannot be called remotely.') % (name,))

def same_name(f, g):
    pass

class MetaModel(api.Meta):
    module_to_models = defaultdict(list)

    def __init__(self, name, bases, attrs):
        if not self._register:
            self._register = True
            super(MetaModel, self).__init__(name, bases, attrs)
            return

        if not hasattr(self, '_module'):
            self._module = self._get_addon_name(self.__module__)

        if not self._custom:
            self.module_to_models[self._module].append(self)

        for key, val in attrs.items():
            if type(val) is tuple and len(val) == 1 and isinstance(val[0], Field):
                _logger.error("Trailing comma after field definition: %s.%s", self, key)
            if isinstance(val, Field):
                val.args = dict(val.args, _module=self._module)


    def _get_addon_name(self, full_name):
        module_parts = full_name.split('.')
        if len(module_parts) > 2 and module_parts[:2] == ['odoo', 'addons']:
            addon_name = full_name.split('.')[2]
        else:
            addon_name = full_name.split('.')[0]
        return addon_name


class NewId(object):
    __slots__ = ['ref']

    def __init__(self, ref=None):
        pass

    def __bool__(self):
        return False

    __nonzero__ = __bool__

IdType = pycompat.integer_types + pycompat.string_types + (NewId,)

PREFETCH_MAX = 1000
LOG_ACCESS_COLUMNS = ['create_uid', 'create_date', 'write_uid', 'write_date']
MAGIC_COLUMNS = ['id'] + LOG_ACCESS_COLUMNS

VALID_AGGREGATE_FUNCTIONS = {
    'array_agg', 'count', 'count_distinct',
    'bool_and', 'bool_or', 'max', 'min', 'avg', 'sum',
}
@pycompat.implements_to_string
class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
    _auto = False  # don't create any database backend
    _register = False  # not visible in ORM registry
    _abstract = True  # whether model is abstract
    _transient = False  # whether model is transient

    _name = None  # the model name
    _description = None  # the model's informal name
    _custom = False  # should be True for custom models only

    _inherit = None  # Python-inherited models ('model' or ['model'])
    _inherits = {}  # inherited models {'parent_model': 'm2o_field'}
    _constraints = []  # Python constraints (old API)

    _table = None  # SQL table name used by model
    _sequence = None  # SQL sequence to use for ID field
    _sql_constraints = []  # SQL constraints [(name, sql_def, message)]

    _rec_name = None  # field to use for labeling records
    _order = 'id'  # default order for searching results
    _parent_name = 'parent_id'  # the many2one field used as parent field
    _parent_store = False  # set to True to compute parent_path field
    _date_name = 'date'  # field to use for default calendar view
    _fold_name = 'fold'  # field to determine folded groups in kanban views

    _needaction = False  # whether the model supports "need actions" (see mail)
    _translate = True  # False disables translations export for this model

    _depends = {}  # dependencies of models backed up by sql views
    # {model_name: field_names, ...}

    # default values for _transient_vacuum()
    _transient_check_count = 0
    _transient_max_count = lazy_classproperty(lambda _: config.get('osv_memory_count_limit'))
    _transient_max_hours = lazy_classproperty(lambda _: config.get('osv_memory_age_limit'))

    CONCURRENCY_CHECK_FIELD = '__last_update'

    @api.model
    def _setup_base(self):
        cls = type(self)
        if cls._setup_done:
            return

        if cls._rec_name:
            assert cls._rec_name in cls._fields, \
                "Invalid rec_name %s for model %s" % (cls._rec_name, cls._name)
        elif 'name' in cls._fields:
            cls._rec_name = 'name'
        elif cls._custom and 'x_name' in cls._fields:
            cls._rec_name = 'x_name'

    @api.model_cr_context
    def _reflect(self):
        pass

    @api.model
    def _add_field(self, name, field):
        pass

    @api.model
    def _pop_field(self, name):
        pass

    @api.model
    def _add_magic_fields(self):
        pass

    def compute_concurrency_field(self):
        pass

    @api.depends('create_date', 'write_date')
    def compute_concurrency_field_with_access(self):
        pass

    @classmethod
    def _build_model(cls, pool, cr):
        pass

    @classmethod
    def _build_model_check_base(model_class, cls):
        pass

    @classmethod
    def _build_model_check_parent(model_class, cls, parent_class):
        pass

    @classmethod
    def _build_model_attributes(cls, pool):
        pass

    @classmethod
    def _init_constraints_onchanges(cls):
        pass

    @property
    def _constraint_methods(self):
        pass

    @property
    def _onchange_methods(self):
        pass

    def __new__(cls):
        pass

    def __init__(self, pool, cr):
        pass

    @api.model
    @ormcache()
    def _is_an_ordinary_table(self):
        pass

    def __ensure_xml_id(self, skip=False):
        pass

    @api.multi
    def _export_rows(self, fields, *, _is_toplevel_call=True):
        pass

    __export_rows = _export_rows

    @api.multi
    def export_data(self, fields_to_export, raw_data=False):
        pass

    @api.model
    def load(self, fields, data):
        pass

    def _add_fake_fields(self, fields):
        pass

    @api.model
    def _extract_records(self, fields_, data, log=lambda a: None):
        pass

    @api.model
    def _convert_records(self, records, log=lambda a: None):
        pass

    @api.multi
    def _validate_fields(self, field_names):
        pass

    @api.model
    def default_get(self, fields_list):
        pass

    @api.model
    def fields_get_keys(self):
        pass

    @api.model
    def _rec_name_fallback(self):
        pass

    @api.model
    def view_header_get(self, view_id=None, view_type='form'):
        pass

    @api.model
    def user_has_groups(self, groups):
        pass

    @api.model
    def _get_default_form_view(self):
        pass

    @api.model
    def _get_default_search_view(self):
        pass

    @api.model
    def _get_default_tree_view(self):
        pass

    @api.model
    def _get_default_activity_view(self):
        pass

    @api.model
    def _get_default_pivot_view(self):
        pass

    @api.model
    def _get_default_kanban_view(self):
        pass

    @api.model
    def _get_default_graph_view(self):
        pass

    @api.model
    def _get_default_calendar_view(self):
        pass

    @api.model
    def load_views(self, views, options=None):
        pass

    @api.model
    def _fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
        pass

    @api.model
    def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
        pass

    @api.multi
    def get_formview_id(self, access_uid=None):
        pass

    @api.multi
    def get_formview_action(self, access_uid=None):
        pass

    @api.multi
    def get_access_action(self, access_uid=None):
        pass

    @api.model
    def search_count(self, args):
        pass

    @api.model
    @api.returns('self',
                 upgrade=lambda self, value, args, offset=0, limit=None, order=None,count=False: value if count else self.browse(value),
                 downgrade=lambda self, value, args, offset=0, limit=None, order=None,count=False: value if count else value.ids)
    def search(self, args, offset=0, limit=None, order=None, count=False):
        pass

    @api.depends(lambda self: (self._rec_name,) if self._rec_name else ())
    def _compute_display_name(self):
        pass

    @api.multi
    def name_get(self):
        pass

    @api.model
    def name_create(self, name):
        pass

    @api.model
    def name_search(self, name='', args=None, operator='ilike', limit=100):
        pass

    @api.model
    def _name_search(self, name='', args=None, operator='ilike', limit=100, name_get_uid=None):
        pass

    @api.model
    def _add_missing_default_values(self, values):
        pass

    @classmethod
    def clear_caches(cls):
        pass

    @api.model
    def _read_group_fill_results(self, domain, groupby, remaining_groupbys,
                                 aggregated_fields, count_field,
                                 read_group_result, read_group_order=None):
        pass

    @api.model
    def _read_group_fill_temporal(self, data, groupby, aggregated_fields, annotated_groupbys,
                                  interval=dateutil.relativedelta.relativedelta(months=1)):
        pass

    @api.model
    def _read_group_prepare(self, orderby, aggregated_fields, annotated_groupbys, query):
        pass

    @api.model
    def _read_group_process_groupby(self, gb, query):
        pass

    @api.model
    def _read_group_prepare_data(self, key, value, groupby_dict):
        pass

    @api.model
    def _read_group_format_result(self, data, annotated_groupbys, groupby, domain):
        pass

    @api.model
    def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
        pass

    @api.model
    def _read_group_raw(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
        pass

    def _read_group_resolve_many2one_fields(self, data, fields):
        pass

    def _inherits_join_add(self, current_model, parent_model_name, query):
        pass

    @api.model
    def _inherits_join_calc(self, alias, fname, query, implicit=True, outer=False):
        pass

    @api.model_cr
    def _parent_store_compute(self):
        pass

    @api.model_cr
    def _check_removed_columns(self, log=False):
        pass

    @api.model_cr_context
    def _init_column(self, column_name):
        pass

    @ormcache()
    def _table_has_rows(self):
        pass

    @api.model_cr_context
    def _auto_init(self):
        pass

    @api.model_cr
    def init(self):
        pass

    @api.model_cr
    def _create_parent_columns(self):
        pass

    @api.model_cr
    def _add_sql_constraints(self):
        pass

    @api.model_cr
    def _execute_sql(self):
        pass

    @api.model
    def _add_inherited_fields(self):
        pass

    @api.model
    def _inherits_check(self):
        pass

    @api.model
    def _prepare_setup(self):
        pass

    @api.model
    def _setup_base(self):
        pass

    @api.model
    def _setup_fields(self):
        pass

    @api.model
    def _setup_complete(self):
        pass

    @api.model
    def fields_get(self, allfields=None, attributes=None):
        pass

    @api.model
    def get_empty_list_help(self, help):
        pass

    @api.model
    def check_field_access_rights(self, operation, fields):
        pass

    @api.multi
    def read(self, fields=None, load='_classic_read'):
        pass

    @api.multi
    def _prefetch_field(self, field):
        pass

    @api.multi
    def _read_from_database(self, field_names, inherited_field_names=[]):
        pass

    @api.multi
    def get_metadata(self):
        pass

    @api.multi
    def _check_concurrency(self):
        pass

    @api.model
    def check_access_rights(self, operation, raise_exception=True):
        pass

    @api.multi
    def check_access_rule(self, operation):
        pass

    def _filter_access_rules(self, operation):
        pass

    @api.multi
    def unlink(self):
        pass

    @api.multi
    def write(self, vals):
        pass

    @api.multi
    def _write(self, vals):
        pass

    @api.model_create_multi
    @api.returns('self', lambda value: value.id)
    def create(self, vals_list):
        pass

    @api.model
    def _create(self, data_list):
        pass

    def _parent_store_create(self):
        pass

    def _parent_store_update_prepare(self, vals):
        pass

    def _parent_store_update(self):
        pass

    def _load_records_write(self, values):
        pass

    def _load_records_create(self, values):
        pass

    def _load_records(self, data_list, update=False):
        pass

    @api.model
    def _where_calc(self, domain, active_test=True):
        pass

    def _check_qorder(self, word):
        pass

    @api.model
    def _apply_ir_rules(self, query, mode='read'):
        pass

    @api.model
    def _generate_translated_field(self, table_alias, field, query):
        pass

    @api.model
    def _generate_m2o_order_by(self, alias, order_field, query, reverse_direction, seen):
        pass

    @api.model
    def _generate_order_by_inner(self, alias, order_spec, query, reverse_direction=False, seen=None):
        pass

    @api.model
    def _generate_order_by(self, order_spec, query):
        pass

    @api.model
    def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
        pass

    @api.multi
    @api.returns(None, lambda value: value[0])
    def copy_data(self, default=None):
        pass

    @api.multi
    def copy_translations(old, new, excluded=()):
        pass

    @api.multi
    @api.returns('self', lambda value: value.id)
    def copy(self, default=None):
        pass

    @api.multi
    @api.returns('self')
    def exists(self):
        pass

    @api.multi
    def _check_recursion(self, parent=None):
        pass

    @api.multi
    def _check_m2m_recursion(self, field_name):
        pass

    @api.multi
    def _get_external_ids(self):
        pass

    @api.multi
    def get_external_id(self):
        pass

    get_xml_id = get_external_id
    _get_xml_ids = _get_external_ids

    @classmethod
    def is_transient(cls):
        pass

    @api.model_cr
    def _transient_clean_rows_older_than(self, seconds):
        pass

    @api.model_cr
    def _transient_clean_old_rows(self, max_count):
        pass

    @api.model
    def _transient_vacuum(self, force=False):
        pass

    @api.model
    def resolve_2many_commands(self, field_name, commands, fields=None):
        pass

    resolve_o2m_commands_to_record_dicts = resolve_2many_commands

    @api.model
    def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None):
        pass

    @api.multi
    def toggle_active(self):
        pass

    @api.model_cr
    def _register_hook(self):
        pass

    def _unregister_hook(self):
        """ Clean up what `~._register_hook` has done. """
        pass

    @classmethod
    def _patch_method(cls, name, method):
        pass

    @classmethod
    def _revert_method(cls, name):
        pass

    @classmethod
    def _browse(cls, ids, env, prefetch=None, add_prefetch=True):
        pass

    def browse(self, arg=None, prefetch=None):
        pass

    @property
    def ids(self):
        pass

    _cr = property(lambda self: self.env.cr)
    _uid = property(lambda self: self.env.uid)
    _context = property(lambda self: self.env.context)

    def ensure_one(self):
        pass

    def with_env(self, env):
        pass

    def sudo(self, user=SUPERUSER_ID):
        pass

    def with_context(self, *args, **kwargs):
        pass

    def with_prefetch(self, prefetch=None):
        pass

    def _convert_to_cache(self, values, update=False, validate=True):
        pass

    def _convert_to_record(self, values):
        pass

    def _convert_to_write(self, values):
        pass

    def _mapped_func(self, func):
        pass

    def mapped(self, func):
        pass

    def _mapped_cache(self, name_seq):
        pass

    def filtered(self, func):
        pass

    def sorted(self, key=None, reverse=False):
        pass

    @api.multi
    def update(self, values):
        pass

    @api.model
    def new(self, values={}, ref=None):
        pass

    def _is_dirty(self):
        pass

    def _get_dirty(self):
        pass

    def _set_dirty(self, field_name):
        pass

    def __bool__(self):
        pass

    __nonzero__ = __bool__

    def __len__(self):
        pass

    def __iter__(self):
        pass

    def __contains__(self, item):
        pass

    def __add__(self, other):
        pass

    def concat(self, *args):
        pass

    def __sub__(self, other):
        pass

    def __and__(self, other):
        pass

    def __or__(self, other):
        pass

    def union(self, *args):
        pass

    def __eq__(self, other):
        pass

    def __lt__(self, other):
        pass

    def __le__(self, other):
        pass

    def __gt__(self, other):
        pass

    def __ge__(self, other):
        pass

    def __int__(self):
        pass

    def __str__(self):
        pass

    def __repr__(self):
        pass

    def __hash__(self):
        pass

    def __getitem__(self, key):
        pass

    def __setitem__(self, key, value):
        pass

    @lazy_property
    def _cache(self):
        pass

    @api.model
    def _in_cache_without(self, field, limit=PREFETCH_MAX):
        pass

    @api.model
    def refresh(self):
        pass

    @api.model
    def invalidate_cache(self, fnames=None, ids=None):
        pass

    @api.multi
    def modified(self, fnames):
        pass

    def _recompute_check(self, field):
        pass

    def _recompute_todo(self, field):
        pass

    def _recompute_done(self, field):
        pass

    @api.model
    def recompute(self):
        pass

    def _has_onchange(self, field, other_fields):
        pass

    @api.model
    def _onchange_spec(self, view_info=None):
        pass

    def _onchange_eval(self, field_name, onchange, result):
        pass

    @api.multi
    def onchange(self, values, field_name, field_onchange):
        pass

collections.Set.register(BaseModel)
collections.Sequence.register(BaseModel)

class RecordCache(MutableMapping):
    def __init__(self, record):
        pass

    def __contains__(self, name):
        pass

    def __getitem__(self, name):
        pass

    def __setitem__(self, name, value):
        pass

    def __delitem__(self, name):
        pass

    def __iter__(self):
        pass

    def __len__(self):
        pass

    def has_value(self, name):
        pass

    def get_value(self, name, default=None):
        pass

    def set_special(self, name, getter):
        pass

    def set_failed(self, names, exception):
        pass

AbstractModel = BaseModel

class Model(AbstractModel):
    _auto = True  # automatically create database backend
    _register = False  # not visible in ORM registry, meant to be python-inherited only
    _abstract = False  # not abstract
    _transient = False

class TransientModel(Model):
    _auto = True  # automatically create database backend
    _register = False  # not visible in ORM registry, meant to be python-inherited only
    _abstract = False  # not abstract
    _transient = True

def itemgetter_tuple(items):
    pass

def convert_pgerror_not_null(model, fields, info, e):
    pass

def convert_pgerror_unique(model, fields, info, e):
    pass

def _get_translated_field_name(model, field_name):
    pass

PGERROR_TO_OE = defaultdict(
    lambda: (lambda model, fvg, info, pgerror: {'message': tools.ustr(pgerror)}), {
    '23502': convert_pgerror_not_null,
    '23505': convert_pgerror_unique,
})

def _normalize_ids(arg, atoms=set(IdType)):
    pass

def lazy_name_get(self):
    pass

from .osv import expression
from .fields import Field